#include "RequestParser.h"
#include "Request.h"


RequestParser::RequestParser()
	: _state(MethodStart)
{
}

void RequestParser::Reset()
{
	_state = MethodStart;
}

boost::tribool RequestParser::Consume(Request &req, char input)
{
	switch (_state)
	{
	case MethodStart:
		if (!isChar(input) || isCtl(input) || isTspecial(input))
		{
			return false;
		}
		else
		{
			_state = Method;
			req.method.push_back(input);
			return boost::indeterminate;
		}

	case Method:
		if (input == ' ')
		{
			_state = Uri;
			return boost::indeterminate;
		}
		else if (!isChar(input) || isCtl(input) || isTspecial(input))
		{
			return false;
		}
		else
		{
			req.method.push_back(input);
			return boost::indeterminate;
		}

	case UriStart:
		if (isCtl(input))
		{
			return false;
		}
		else
		{
			_state = Uri;
			req.uri.push_back(input);
			return boost::indeterminate;
		}

	case Uri:
		if (input == ' ')
		{
			_state = HttpVersionH;
			return boost::indeterminate;
		}
		else if (isCtl(input))
		{
			return false;
		}
		else
		{
			req.uri.push_back(input);
			return boost::indeterminate;
		}

	case HttpVersionH:
		if (input == 'H')
		{
			_state = HttpVersionT1;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionT1:
		if (input == 'T')
		{
			_state = HttpVersionT2;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionT2:
		if (input == 'T')
		{
			_state = HttpVersionP;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionP:
		if (input == 'P')
		{
			_state = HttpVersionSlash;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionSlash:
		if (input == '/')
		{
			req.httpVersionMajor = 0;
			req.httpVersionMinor = 0;
			_state = HttpVersionMajorStart;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionMajorStart:
		if (isDigit(input))
		{
			req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
			_state = HttpVersionMajor;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionMajor:
		if (input == '.')
		{
			_state = HttpVersionMinorStart;
			return boost::indeterminate;
		}
		else if (isDigit(input))
		{
			req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionMinorStart:
		if (isDigit(input))
		{
			req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
			_state = HttpVersionMinor;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HttpVersionMinor:
		if (input == '\r')
		{
			_state = ExpectingNewline1;
			return boost::indeterminate;
		}
		else if (isDigit(input))
		{
			req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case ExpectingNewline1:
		if (input == '\n')
		{
			_state = HeaderLineStart;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HeaderLineStart:
		if (input == '\r')
		{
			_state = ExpectingNewline3;
			return boost::indeterminate;
		}
		else if (!req.headers.empty() && (input == ' ' || input == '\t'))
		{
			_state = HeaderLws;
			return boost::indeterminate;
		}
		else if (!isChar(input) || isCtl(input) || isTspecial(input))
		{
			return false;
		}
		else
		{
			req.headers.push_back(Header());
			req.headers.back().name.push_back(input);
			_state = HeaderName;
			return boost::indeterminate;
		}

	case HeaderLws:
		if (input == '\r')
		{
			_state = ExpectingNewline2;
			return boost::indeterminate;
		}
		else if (input == ' ' || input == '\t')
		{
			return boost::indeterminate;
		}
		else if (isCtl(input))
		{
			return false;
		}
		else
		{
			_state = HeaderValue;
			req.headers.back().value.push_back(input);
			return boost::indeterminate;
		}

	case HeaderName:
		if (input == ':')
		{
			_state = SpaceBeforeHeaderValue;
			return boost::indeterminate;
		}
		else if (!isChar(input) || isCtl(input) || isTspecial(input))
		{
			return false;
		}
		else
		{
			req.headers.back().name.push_back(input);
			return boost::indeterminate;
		}

	case SpaceBeforeHeaderValue:
		if (input == ' ')
		{
			_state = HeaderValue;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case HeaderValue:
		if (input == '\r')
		{
			_state = ExpectingNewline2;
			return boost::indeterminate;
		}
		else if (isCtl(input))
		{
			return false;
		}
		else
		{
			req.headers.back().value.push_back(input);
			return boost::indeterminate;
		}

	case ExpectingNewline2:
		if (input == '\n')
		{
			_state = HeaderLineStart;
			return boost::indeterminate;
		}
		else
		{
			return false;
		}

	case ExpectingNewline3:
		return (input == '\n');

	default:
		return false;
	}
}

bool RequestParser::isChar(int c)
{
	return c >= 0 && c <= 127;
}

bool RequestParser::isCtl(int c)
{
	return c >= 0 && c <= 31 || c == 127;
}

bool RequestParser::isTspecial(int c)
{
	switch (c)
	{
	case '(': case ')': case '<': case '>': case '@':
	case ',': case ';': case ':': case '\\': case '"':
	case '/': case '[': case ']': case '?': case '=':
	case '{': case '}': case ' ': case '\t':
		return true;

	default:
		return false;
	}
}

bool RequestParser::isDigit(int c)
{
	return c >= '0' && c <= '9';
}